library(sf)
library(tidyverse)

Part 1: Load and prepare burglary data

We begin by loading the aggravated burglary data for 2023 and removing records that are missing location coordinates.

burglaries <- read_csv("../data/burglaries_2023.csv")
burglaries_clean <- burglaries |>
  filter(!is.na(latitude), !is.na(longitude))

Convert to an sf object

We convert the cleaned data into a spatial object using longitude and latitude.

burglaries_sf <- st_as_sf(
  burglaries_clean,
  coords = c("longitude", "latitude"),
  crs = 4326,
  remove = FALSE
)
burglaries_sf
Simple feature collection with 1146 features and 29 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -92.51 ymin: 34.15 xmax: -86.557 ymax: 36.34
Geodetic CRS:  WGS 84

Read the census data

census <- read_csv("../data/census.csv")
census
census <- census |>
  mutate(
    median_income = if_else(median_income < 0, NA_real_, median_income)
  )

When I started plotting the census data, I noticed that the median_income values looked incorrect. Some tracts had extremely large negative numbers (for example, –666,666,666). These are not real incomes—they are special codes the Census uses to show missing data.

To fix this and get accurate plots and results, I changed all negative income values to NA before continuing the analysis.

Read the DC shape file data

dc_tracts <- st_read("../data/DC/DC.shp")
Reading layer `DC' from data source `C:\Users\dhans\Documents\DataScience\Program\NSS_projects\geospatial-r-dhansiv-stack\data\DC\DC.shp' using driver `ESRI Shapefile'
Simple feature collection with 174 features and 12 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -87.0547 ymin: 35.96778 xmax: -86.51559 ymax: 36.4055
Geodetic CRS:  NAD83

Before we can perform a spatial join, we need to make sure both datasets use the same coordinate reference system (CRS). The burglary points are in WGS84 (EPSG:4326), while the census tracts use NAD83 (EPSG:4269). We transform the burglary data to match the tract CRS.


burglaries_sf_4269 <- st_transform(burglaries_sf, st_crs(dc_tracts))

Now that both datasets use the same CRS, we can perform a spatial join. We use st_join() with st_within() to determine which census tract each burglary point falls inside. This attaches the tract information (such as the GEOID) to every burglary incident.

burg_with_tract <- st_join(
  burglaries_sf_4269,  
  dc_tracts,           
  join = st_within     
)
burg_with_tract
Simple feature collection with 1146 features and 41 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -92.51 ymin: 34.15 xmax: -86.557 ymax: 36.34
Geodetic CRS:  NAD83

Before we can summarize the number of burglaries in each census tract, we need to remove the geometry so that the data behaves like a regular table. Each incident may appear multiple times (for example, if there are multiple victims), so we also remove duplicate incident numbers. After cleaning, we count the number of unique burglary incidents in each census tract.


burg_nogeome <- burg_with_tract |> st_drop_geometry()

burg_unique <- burg_nogeome |> 
  distinct(incident_number, GEOID, .keep_all = TRUE)

burg_counts <- burg_unique |>
  count(GEOID, name = "burglaries")

head(burg_counts)

Create GEOID in the census data

We construct a GEOID field in the census data by combining the state, county, and tract codes in the same format used in the tract shapefile.

census <- census |>
  mutate(
    state_chr = sprintf("%02.0f", state),   
    GEOID = paste0(state_chr, county, tract)
  )

head(census)

Because the GEOID requires a text string, the numeric state code (47) is converted into a two-digit string (‘47’) using the sprintf() function.

census_burg <- census |>
  left_join(burg_counts, by = "GEOID") |>
  mutate(
    burglaries = replace_na(burglaries, 0)
  )
head(census_burg)

Finally, we join the census and burglary attributes back to the tract shapefile so that we have a complete spatial dataset for mapping and modeling.

tract_data_sf <- dc_tracts |> left_join(census_burg, by = "GEOID")
head(tract_data_sf)
Simple feature collection with 6 features and 20 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -86.97461 ymin: 36.16132 xmax: -86.6693 ymax: 36.4055
Geodetic CRS:  NAD83
  STATEFP COUNTYFP TRACTCE       GEOID NAME.x            NAMELSAD MTFCC FUNCSTAT    ALAND AWATER    INTPTLAT     INTPTLON                                          NAME.y
1      47      037  010103 47037010103 101.03 Census Tract 101.03 G5020        S 48034082  61097 +36.3444054 -086.8608396 Census Tract 101.03, Davidson County, Tennessee
2      47      037  010202 47037010202 102.02 Census Tract 102.02 G5020        S 68394934  77571 +36.3619781 -086.7746355 Census Tract 102.02, Davidson County, Tennessee
3      47      037  010104 47037010104 101.04 Census Tract 101.04 G5020        S 65057849 251504 +36.2940028 -086.8777483 Census Tract 101.04, Davidson County, Tennessee
4      47      037  010106 47037010106 101.06 Census Tract 101.06 G5020        S 21616474   6845 +36.2610013 -086.8023491 Census Tract 101.06, Davidson County, Tennessee
5      47      037  010602 47037010602 106.02 Census Tract 106.02 G5020        S  3097282      0 +36.2533653 -086.6817552 Census Tract 106.02, Davidson County, Tennessee
6      47      037  019200 47037019200    192    Census Tract 192 G5020        S  1771200      0 +36.1711529 -086.7520191    Census Tract 192, Davidson County, Tennessee
  state county  tract population median_income state_chr burglaries                       geometry
1    47    037 010103       2411         60000        47          2 MULTIPOLYGON (((-86.91752 3...
2    47    037 010202       3919         81695        47          1 MULTIPOLYGON (((-86.82483 3...
3    47    037 010104       3002         84831        47          5 MULTIPOLYGON (((-86.9744 36...
4    47    037 010106       2948         66940        47          2 MULTIPOLYGON (((-86.83089 3...
5    47    037 010602       3688         49173        47          3 MULTIPOLYGON (((-86.6953 36...
6    47    037 019200       3853         64760        47          2 MULTIPOLYGON (((-86.76277 3...

Part 2: Exploratory Analysis

tract_data_sf <- tract_data_sf |>
  rename(NAME = NAME.x) |>
  select(-NAME.y)

tract_data_sf
Simple feature collection with 174 features and 19 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -87.0547 ymin: 35.96778 xmax: -86.51559 ymax: 36.4055
Geodetic CRS:  NAD83
First 10 features:
   STATEFP COUNTYFP TRACTCE       GEOID   NAME            NAMELSAD MTFCC FUNCSTAT    ALAND  AWATER    INTPTLAT     INTPTLON state county  tract population median_income
1       47      037  010103 47037010103 101.03 Census Tract 101.03 G5020        S 48034082   61097 +36.3444054 -086.8608396    47    037 010103       2411         60000
2       47      037  010202 47037010202 102.02 Census Tract 102.02 G5020        S 68394934   77571 +36.3619781 -086.7746355    47    037 010202       3919         81695
3       47      037  010104 47037010104 101.04 Census Tract 101.04 G5020        S 65057849  251504 +36.2940028 -086.8777483    47    037 010104       3002         84831
4       47      037  010106 47037010106 101.06 Census Tract 101.06 G5020        S 21616474    6845 +36.2610013 -086.8023491    47    037 010106       2948         66940
5       47      037  010602 47037010602 106.02 Census Tract 106.02 G5020        S  3097282       0 +36.2533653 -086.6817552    47    037 010602       3688         49173
6       47      037  019200 47037019200    192    Census Tract 192 G5020        S  1771200       0 +36.1711529 -086.7520191    47    037 019200       3853         64760
7       47      037  015637 47037015637 156.37 Census Tract 156.37 G5020        S  9828657 1204178 +36.1045801 -086.6411995    47    037 015637       3768         52821
8       47      037  015633 47037015633 156.33 Census Tract 156.33 G5020        S 13138415 4907139 +36.1451761 -086.5766361    47    037 015633       4704        108883
9       47      037  015636 47037015636 156.36 Census Tract 156.36 G5020        S  2194409       0 +36.0833835 -086.6377772    47    037 015636       2979         73529
10      47      037  019119 47037019119 191.19 Census Tract 191.19 G5020        S 25041367       0 +35.9956858 -086.6512255    47    037 019119       7406        104307
   state_chr burglaries                       geometry
1         47          2 MULTIPOLYGON (((-86.91752 3...
2         47          1 MULTIPOLYGON (((-86.82483 3...
3         47          5 MULTIPOLYGON (((-86.9744 36...
4         47          2 MULTIPOLYGON (((-86.83089 3...
5         47          3 MULTIPOLYGON (((-86.6953 36...
6         47          2 MULTIPOLYGON (((-86.76277 3...
7         47         10 MULTIPOLYGON (((-86.66648 3...
8         47          1 MULTIPOLYGON (((-86.61354 3...
9         47          2 MULTIPOLYGON (((-86.64844 3...
10        47          6 MULTIPOLYGON (((-86.70088 3...

Perform some exploratory analysis on your prepared dataset.

Using the tract-level dataset prepared in Part 1 (which aggregates unique burglary incidents by census tract to avoid double-counting), we now explore patterns in burglary counts and rates.

Which census tract had the highest number of burglaries?

top_burglary_count <- tract_data_sf |>
  arrange(desc(burglaries)) |>
  select(GEOID, NAME, burglaries) |>
  slice(1)
top_burglary_count
Simple feature collection with 1 feature and 3 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -86.77516 ymin: 36.13758 xmax: -86.75074 ymax: 36.15478
Geodetic CRS:  NAD83
        GEOID NAME burglaries                       geometry
1 47037016000  160         39 MULTIPOLYGON (((-86.77516 3...

The census tract with the highest number of aggravated burglaries in 2023 is Tract 160 (GEOID 47037016000), with a total of 39 incidents.

Which census tract had the highest number of burglaries per 1000 residents?

tract_data_sf <- tract_data_sf |>
  mutate(
    burglary_rate_1000 = (burglaries / population) * 1000
  )

top_burglary_rate <- tract_data_sf |>
  filter(population > 0) |>
  arrange(desc(burglary_rate_1000)) |>
  select(GEOID, NAME, burglary_rate_1000, population, burglaries) |>
  slice(1)

top_burglary_rate
Simple feature collection with 1 feature and 5 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -86.77516 ymin: 36.13758 xmax: -86.75074 ymax: 36.15478
Geodetic CRS:  NAD83
        GEOID NAME burglary_rate_1000 population burglaries                       geometry
1 47037016000  160            15.1751       2570         39 MULTIPOLYGON (((-86.77516 3...

The census tract with the highest burglary rate per 1,000 residents is Tract 160 (GEOID 47037016000). This tract reported 39 aggravated burglaries and has a population of 2,570, resulting in a burglary rate of approximately 15.18 incidents per 1,000 residents.

We’re interested in the relationship between median income and number of aggravated burglaries, so examine those variables on their own and together to see what you can find. You may want to perform additional calculations, create plots, etc.

Distribution of median income across census tracts

tract_data_sf |>
  filter(!is.na(median_income)) |>     # remove missing incomes
  ggplot(aes(x = median_income)) +
  geom_histogram(bins = 30, fill = "steelblue") +
  labs(
    title = "Distribution of Median Income by Census Tract",
    x = "Median Income",
    y = "Count of Tracts"
  ) +
  theme_minimal()

The median income varies across census tracts in Davidson County, with most tracts falling between about $40,000 and $80,000. After removing invalid negative values from the Census data, the distribution shows a right-skewed pattern, with a few higher-income tracts on the upper end.

Distribution of aggravated burglaries across census tracts


  ggplot(tract_data_sf, aes(x = burglaries)) +
  geom_histogram(bins = 30, fill = "firebrick") +
  labs(
    title = "Distribution of Aggravated Burglaries",
    x = "Number of Burglaries",
    y = "Count of Census Tracts"
  ) +
  theme_minimal()

Most census tracts experienced relatively few aggravated burglaries in 2023, with the majority falling between 0 and 10 incidents. A small number of tracts had substantially higher counts, creating a right-skewed distribution. This suggests that aggravated burglaries are concentrated in specific areas rather than evenly distributed across the county.

Relationship Between Median Income and Aggravated Burglary Counts

tract_data_sf |>
  filter(!is.na(median_income)) |>     # remove missing incomes
  ggplot(aes(x = median_income, y = burglaries)) +
  geom_point(alpha = 0.7) +
  geom_smooth(method = "lm", formula = y~x, se = FALSE, color = "blue") +
  labs(
    title = "Median Income vs. Aggravated Burglary Counts",
    x = "Median Income",
    y = "Number of Burglaries"
  ) +
  theme_minimal()

To explore whether income is related to aggravated burglary activity, I created a scatterplot comparing median income (x-axis) with the number of burglaries in each census tract (y-axis). Each point represents a census tract, and a linear trend line is included to show the overall direction of the relationship.

Part 3: Statistical Modeling

Fit a Poisson regression model with target variable the rate of burglaries per census tract and with predictor the median income. Offset using the log of the population so that we are looking at the rate of burglaries per population instead of the number of burglaries. How can you interpret the meaning of the output? How do the estimates from the model compare to the observed data?

model1 <- tract_data_sf |>
  st_drop_geometry() |>
  filter(
    population>0,
    !is.na(median_income),
    !is.na(burglaries)
  )

Poisson regression model fit

model2 <- glm(
  burglaries ~ median_income + offset(log(population)),
  data = model1,
  family =poisson()
)

summary(model2)

Call:
glm(formula = burglaries ~ median_income + offset(log(population)), 
    family = poisson(), data = model1)

Coefficients:
                Estimate Std. Error z value Pr(>|z|)    
(Intercept)   -5.184e+00  9.778e-02  -53.01   <2e-16 ***
median_income -2.434e-05  1.686e-06  -14.43   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 838.73  on 169  degrees of freedom
Residual deviance: 577.53  on 168  degrees of freedom
AIC: 1089.4

Number of Fisher Scoring iterations: 5

Interpret the coefficient on median_income

model2 |> coef() |> exp()
  (Intercept) median_income 
   0.00560792    0.99997566 

Income is measured in dollars, so the effect per $1 is too small to interpret. I scaled the coefficient to a $10,000 increase, which makes the rate ratio meaningful while still using the same exponentiation approach taught in class.

exp(10000 * coef(model2)["median_income"])
median_income 
    0.7839851 

For two census tracts with the same population, the tract with a $10,000 higher median income is estimated to have a burglary rate that is about 21.6% lower.

predict(
  model2,
  newdata = tibble(
    median_income = c(30000, 80000),   
    population = c(4000, 4000)        
  ),
  type = "response"
)
        1         2 
10.808993  3.201285 

For two census tracts with the same population of 4,000 people, the model predicts about 10.8 aggravated burglaries in a tract with a median income of $30,000, compared to about 3.2 burglaries in a tract with a median income of $80,000. This reflects the model’s estimate that higher-income areas have substantially lower burglary rates.

Final Summary

LS0tDQp0aXRsZTogIkFuYWx5emluZyBBZ2dyYXZhdGVkIEJ1cmdsYXJpZXMgaW4gRGF2aWRzb24gQ291bnR5Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShzZikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiMjIFBhcnQgMTogTG9hZCBhbmQgcHJlcGFyZSBidXJnbGFyeSBkYXRhDQoNCg0KKipXZSBiZWdpbiBieSBsb2FkaW5nIHRoZSBhZ2dyYXZhdGVkIGJ1cmdsYXJ5IGRhdGEgZm9yIDIwMjMgYW5kIHJlbW92aW5nIHJlY29yZHMgdGhhdCBhcmUgbWlzc2luZyBsb2NhdGlvbiBjb29yZGluYXRlcy4qKg0KDQpgYGB7cn0NCmJ1cmdsYXJpZXMgPC0gcmVhZF9jc3YoIi4uL2RhdGEvYnVyZ2xhcmllc18yMDIzLmNzdiIpDQpidXJnbGFyaWVzX2NsZWFuIDwtIGJ1cmdsYXJpZXMgfD4NCiAgZmlsdGVyKCFpcy5uYShsYXRpdHVkZSksICFpcy5uYShsb25naXR1ZGUpKQ0KYGBgDQoqKkNvbnZlcnQgdG8gYW4gc2Ygb2JqZWN0KioNCg0KKldlIGNvbnZlcnQgdGhlIGNsZWFuZWQgZGF0YSBpbnRvIGEgc3BhdGlhbCBvYmplY3QgdXNpbmcgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZS4qDQoNCmBgYHtyfQ0KYnVyZ2xhcmllc19zZiA8LSBzdF9hc19zZigNCiAgYnVyZ2xhcmllc19jbGVhbiwNCiAgY29vcmRzID0gYygibG9uZ2l0dWRlIiwgImxhdGl0dWRlIiksDQogIGNycyA9IDQzMjYsDQogIHJlbW92ZSA9IEZBTFNFDQopDQpgYGANCg0KYGBge3J9DQpidXJnbGFyaWVzX3NmDQpgYGANCioqUmVhZCB0aGUgY2Vuc3VzIGRhdGEqKg0KDQpgYGB7cn0NCmNlbnN1cyA8LSByZWFkX2NzdigiLi4vZGF0YS9jZW5zdXMuY3N2IikNCmBgYA0KYGBge3J9DQpjZW5zdXMNCmBgYA0KDQpgYGB7cn0NCmNlbnN1cyA8LSBjZW5zdXMgfD4NCiAgbXV0YXRlKA0KICAgIG1lZGlhbl9pbmNvbWUgPSBpZl9lbHNlKG1lZGlhbl9pbmNvbWUgPCAwLCBOQV9yZWFsXywgbWVkaWFuX2luY29tZSkNCiAgKQ0KYGBgDQoqV2hlbiBJIHN0YXJ0ZWQgcGxvdHRpbmcgdGhlIGNlbnN1cyBkYXRhLCBJIG5vdGljZWQgdGhhdCB0aGUgYG1lZGlhbl9pbmNvbWVgIHZhbHVlcyBsb29rZWQgaW5jb3JyZWN0LiogKlNvbWUgdHJhY3RzIGhhZCBleHRyZW1lbHkgbGFyZ2UgbmVnYXRpdmUgbnVtYmVycyAoZm9yIGV4YW1wbGUsIOKAkzY2Niw2NjYsNjY2KS4qICpUaGVzZSBhcmUgbm90IHJlYWwgaW5jb21lc+KAlHRoZXkgYXJlIHNwZWNpYWwgY29kZXMgdGhlIENlbnN1cyB1c2VzIHRvIHNob3cgbWlzc2luZyBkYXRhLioNCg0KKlRvIGZpeCB0aGlzIGFuZCBnZXQgYWNjdXJhdGUgcGxvdHMgYW5kIHJlc3VsdHMsIEkgY2hhbmdlZCBhbGwgbmVnYXRpdmUgaW5jb21lIHZhbHVlcyB0byBgTkFgIGJlZm9yZSBjb250aW51aW5nIHRoZSBhbmFseXNpcy4qDQoNCg0KDQoNCioqUmVhZCB0aGUgREMgc2hhcGUgZmlsZSBkYXRhKioNCg0KYGBge3J9DQpkY190cmFjdHMgPC0gc3RfcmVhZCgiLi4vZGF0YS9EQy9EQy5zaHAiKQ0KYGBgDQoqKkJlZm9yZSB3ZSBjYW4gcGVyZm9ybSBhIHNwYXRpYWwgam9pbiwgd2UgbmVlZCB0byBtYWtlIHN1cmUgYm90aCBkYXRhc2V0cyB1c2UgdGhlIHNhbWUgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtIChDUlMpLioqICoqVGhlIGJ1cmdsYXJ5IHBvaW50cyBhcmUgaW4gV0dTODQgKEVQU0c6NDMyNiksIHdoaWxlIHRoZSBjZW5zdXMgdHJhY3RzIHVzZSBOQUQ4MyAoRVBTRzo0MjY5KS4qKiAqKldlIHRyYW5zZm9ybSB0aGUgYnVyZ2xhcnkgZGF0YSB0byBtYXRjaCB0aGUgdHJhY3QgQ1JTLioqDQpgYGB7cn0NCg0KYnVyZ2xhcmllc19zZl80MjY5IDwtIHN0X3RyYW5zZm9ybShidXJnbGFyaWVzX3NmLCBzdF9jcnMoZGNfdHJhY3RzKSkNCg0KDQpgYGANCioqTm93IHRoYXQgYm90aCBkYXRhc2V0cyB1c2UgdGhlIHNhbWUgQ1JTLCB3ZSBjYW4gcGVyZm9ybSBhIHNwYXRpYWwgam9pbi4qKiAqKldlIHVzZSBzdF9qb2luKCkgd2l0aCBzdF93aXRoaW4oKSB0byBkZXRlcm1pbmUgd2hpY2ggY2Vuc3VzIHRyYWN0IGVhY2ggYnVyZ2xhcnkgcG9pbnQgZmFsbHMgaW5zaWRlLioqICoqVGhpcyBhdHRhY2hlcyB0aGUgdHJhY3QgaW5mb3JtYXRpb24gKHN1Y2ggYXMgdGhlIEdFT0lEKSB0byBldmVyeSBidXJnbGFyeSBpbmNpZGVudC4qKg0KDQoNCmBgYHtyfQ0KYnVyZ193aXRoX3RyYWN0IDwtIHN0X2pvaW4oDQogIGJ1cmdsYXJpZXNfc2ZfNDI2OSwgIA0KICBkY190cmFjdHMsICAgICAgICAgICANCiAgam9pbiA9IHN0X3dpdGhpbiAgICAgDQopDQpidXJnX3dpdGhfdHJhY3QNCmBgYA0KDQoqKkJlZm9yZSB3ZSBjYW4gc3VtbWFyaXplIHRoZSBudW1iZXIgb2YgYnVyZ2xhcmllcyBpbiBlYWNoIGNlbnN1cyB0cmFjdCwgd2UgbmVlZCB0byByZW1vdmUgdGhlIGdlb21ldHJ5IHNvIHRoYXQgdGhlIGRhdGEgYmVoYXZlcyBsaWtlIGEgcmVndWxhciB0YWJsZS4qKiAqKkVhY2ggaW5jaWRlbnQgbWF5IGFwcGVhciBtdWx0aXBsZSB0aW1lcyAoZm9yIGV4YW1wbGUsIGlmIHRoZXJlIGFyZSBtdWx0aXBsZSB2aWN0aW1zKSwgc28gd2UgYWxzbyByZW1vdmUgZHVwbGljYXRlIGluY2lkZW50IG51bWJlcnMuKiogKipBZnRlciBjbGVhbmluZywgd2UgY291bnQgdGhlIG51bWJlciBvZiB1bmlxdWUgYnVyZ2xhcnkgaW5jaWRlbnRzIGluIGVhY2ggY2Vuc3VzIHRyYWN0LioqDQoNCmBgYHtyfQ0KDQpidXJnX25vZ2VvbWUgPC0gYnVyZ193aXRoX3RyYWN0IHw+IHN0X2Ryb3BfZ2VvbWV0cnkoKQ0KDQpidXJnX3VuaXF1ZSA8LSBidXJnX25vZ2VvbWUgfD4gDQogIGRpc3RpbmN0KGluY2lkZW50X251bWJlciwgR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpDQoNCmJ1cmdfY291bnRzIDwtIGJ1cmdfdW5pcXVlIHw+DQogIGNvdW50KEdFT0lELCBuYW1lID0gImJ1cmdsYXJpZXMiKQ0KDQpoZWFkKGJ1cmdfY291bnRzKQ0KYGBgDQogKipDcmVhdGUgR0VPSUQgaW4gdGhlIGNlbnN1cyBkYXRhKioNCg0KKldlIGNvbnN0cnVjdCBhIEdFT0lEIGZpZWxkIGluIHRoZSBjZW5zdXMgZGF0YSBieSBjb21iaW5pbmcgdGhlIHN0YXRlLCBjb3VudHksIGFuZCB0cmFjdCBjb2RlcyBpbiB0aGUgc2FtZSBmb3JtYXQgdXNlZCBpbiB0aGUgdHJhY3Qgc2hhcGVmaWxlLioNCmBgYHtyfQ0KY2Vuc3VzIDwtIGNlbnN1cyB8Pg0KICBtdXRhdGUoDQogICAgc3RhdGVfY2hyID0gc3ByaW50ZigiJTAyLjBmIiwgc3RhdGUpLCAgIA0KICAgIEdFT0lEID0gcGFzdGUwKHN0YXRlX2NociwgY291bnR5LCB0cmFjdCkNCiAgKQ0KDQpoZWFkKGNlbnN1cykNCmBgYA0KDQoNCg0KDQoqQmVjYXVzZSB0aGUgR0VPSUQgcmVxdWlyZXMgYSB0ZXh0IHN0cmluZywgdGhlIG51bWVyaWMgc3RhdGUgY29kZSAoNDcpIGlzIGNvbnZlcnRlZCBpbnRvIGEgdHdvLWRpZ2l0IHN0cmluZyAo4oCYNDfigJkpIHVzaW5nIHRoZSBzcHJpbnRmKCkgZnVuY3Rpb24uKg0KDQpgYGB7cn0NCmNlbnN1c19idXJnIDwtIGNlbnN1cyB8Pg0KICBsZWZ0X2pvaW4oYnVyZ19jb3VudHMsIGJ5ID0gIkdFT0lEIikgfD4NCiAgbXV0YXRlKA0KICAgIGJ1cmdsYXJpZXMgPSByZXBsYWNlX25hKGJ1cmdsYXJpZXMsIDApDQogICkNCmhlYWQoY2Vuc3VzX2J1cmcpDQpgYGANCg0KKkZpbmFsbHksIHdlIGpvaW4gdGhlIGNlbnN1cyBhbmQgYnVyZ2xhcnkgYXR0cmlidXRlcyBiYWNrIHRvIHRoZSB0cmFjdCBzaGFwZWZpbGUgc28gdGhhdCB3ZSBoYXZlIGEgY29tcGxldGUgc3BhdGlhbCBkYXRhc2V0IGZvciBtYXBwaW5nIGFuZCBtb2RlbGluZy4qDQoNCmBgYHtyfQ0KdHJhY3RfZGF0YV9zZiA8LSBkY190cmFjdHMgfD4gbGVmdF9qb2luKGNlbnN1c19idXJnLCBieSA9ICJHRU9JRCIpDQpoZWFkKHRyYWN0X2RhdGFfc2YpDQpgYGANCiMjIyBQYXJ0IDI6IEV4cGxvcmF0b3J5IEFuYWx5c2lzDQoNCmBgYHtyfQ0KdHJhY3RfZGF0YV9zZiA8LSB0cmFjdF9kYXRhX3NmIHw+DQogIHJlbmFtZShOQU1FID0gTkFNRS54KSB8Pg0KICBzZWxlY3QoLU5BTUUueSkNCg0KdHJhY3RfZGF0YV9zZg0KYGBgDQoNCioqUGVyZm9ybSBzb21lIGV4cGxvcmF0b3J5IGFuYWx5c2lzIG9uIHlvdXIgcHJlcGFyZWQgZGF0YXNldC4qKg0KDQoqKlVzaW5nIHRoZSB0cmFjdC1sZXZlbCBkYXRhc2V0IHByZXBhcmVkIGluIFBhcnQgMSAod2hpY2ggYWdncmVnYXRlcyB1bmlxdWUgYnVyZ2xhcnkgaW5jaWRlbnRzIGJ5IGNlbnN1cyB0cmFjdCB0byBhdm9pZCBkb3VibGUtY291bnRpbmcpLCB3ZSBub3cgZXhwbG9yZSBwYXR0ZXJucyBpbiBidXJnbGFyeSBjb3VudHMgYW5kIHJhdGVzLioqDQoNCipXaGljaCBjZW5zdXMgdHJhY3QgaGFkIHRoZSBoaWdoZXN0IG51bWJlciBvZiBidXJnbGFyaWVzPyoNCmBgYHtyfQ0KdG9wX2J1cmdsYXJ5X2NvdW50IDwtIHRyYWN0X2RhdGFfc2YgfD4NCiAgYXJyYW5nZShkZXNjKGJ1cmdsYXJpZXMpKSB8Pg0KICBzZWxlY3QoR0VPSUQsIE5BTUUsIGJ1cmdsYXJpZXMpIHw+DQogIHNsaWNlKDEpDQp0b3BfYnVyZ2xhcnlfY291bnQNCmBgYA0KKlRoZSBjZW5zdXMgdHJhY3Qgd2l0aCB0aGUgaGlnaGVzdCBudW1iZXIgb2YgYWdncmF2YXRlZCBidXJnbGFyaWVzIGluIDIwMjMgaXMgVHJhY3QgMTYwIChHRU9JRCA0NzAzNzAxNjAwMCksIHdpdGggYSB0b3RhbCBvZiAzOSBpbmNpZGVudHMuKg0KDQoNCipXaGljaCBjZW5zdXMgdHJhY3QgaGFkIHRoZSBoaWdoZXN0IG51bWJlciBvZiBidXJnbGFyaWVzIHBlciAxMDAwIHJlc2lkZW50cz8qDQoNCmBgYHtyfQ0KdHJhY3RfZGF0YV9zZiA8LSB0cmFjdF9kYXRhX3NmIHw+DQogIG11dGF0ZSgNCiAgICBidXJnbGFyeV9yYXRlXzEwMDAgPSAoYnVyZ2xhcmllcyAvIHBvcHVsYXRpb24pICogMTAwMA0KICApDQoNCnRvcF9idXJnbGFyeV9yYXRlIDwtIHRyYWN0X2RhdGFfc2YgfD4NCiAgZmlsdGVyKHBvcHVsYXRpb24gPiAwKSB8Pg0KICBhcnJhbmdlKGRlc2MoYnVyZ2xhcnlfcmF0ZV8xMDAwKSkgfD4NCiAgc2VsZWN0KEdFT0lELCBOQU1FLCBidXJnbGFyeV9yYXRlXzEwMDAsIHBvcHVsYXRpb24sIGJ1cmdsYXJpZXMpIHw+DQogIHNsaWNlKDEpDQoNCnRvcF9idXJnbGFyeV9yYXRlDQoNCmBgYA0KKlRoZSBjZW5zdXMgdHJhY3Qgd2l0aCB0aGUgaGlnaGVzdCBidXJnbGFyeSByYXRlIHBlciAxLDAwMCByZXNpZGVudHMgaXMgVHJhY3QgMTYwIChHRU9JRCA0NzAzNzAxNjAwMCkuIFRoaXMgdHJhY3QgcmVwb3J0ZWQgMzkgYWdncmF2YXRlZCBidXJnbGFyaWVzIGFuZCBoYXMgYSBwb3B1bGF0aW9uIG9mIDIsNTcwLCByZXN1bHRpbmcgaW4gYSBidXJnbGFyeSByYXRlIG9mIGFwcHJveGltYXRlbHkgMTUuMTggaW5jaWRlbnRzIHBlciAxLDAwMCByZXNpZGVudHMuKg0KDQoNCioqV2UncmUgaW50ZXJlc3RlZCBpbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbWVkaWFuIGluY29tZSBhbmQgbnVtYmVyIG9mIGFnZ3JhdmF0ZWQgYnVyZ2xhcmllcywgc28gZXhhbWluZSB0aG9zZSB2YXJpYWJsZXMgb24gdGhlaXIgb3duIGFuZCB0b2dldGhlciB0byBzZWUgd2hhdCB5b3UgY2FuIGZpbmQuIFlvdSBtYXkgd2FudCB0byBwZXJmb3JtIGFkZGl0aW9uYWwgY2FsY3VsYXRpb25zLCBjcmVhdGUgcGxvdHMsIGV0Yy4qKg0KDQoqRGlzdHJpYnV0aW9uIG9mIG1lZGlhbiBpbmNvbWUgYWNyb3NzIGNlbnN1cyB0cmFjdHMqDQpgYGB7cn0NCnRyYWN0X2RhdGFfc2YgfD4NCiAgZmlsdGVyKCFpcy5uYShtZWRpYW5faW5jb21lKSkgfD4gICAgICMgcmVtb3ZlIG1pc3NpbmcgaW5jb21lcw0KICBnZ3Bsb3QoYWVzKHggPSBtZWRpYW5faW5jb21lKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBNZWRpYW4gSW5jb21lIGJ5IENlbnN1cyBUcmFjdCIsDQogICAgeCA9ICJNZWRpYW4gSW5jb21lIiwNCiAgICB5ID0gIkNvdW50IG9mIFRyYWN0cyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoqVGhlIG1lZGlhbiBpbmNvbWUgdmFyaWVzIGFjcm9zcyBjZW5zdXMgdHJhY3RzIGluIERhdmlkc29uIENvdW50eSwgd2l0aCBtb3N0IHRyYWN0cyBmYWxsaW5nIGJldHdlZW4gYWJvdXQgJDQwLDAwMCBhbmQgJDgwLDAwMC4gQWZ0ZXIgcmVtb3ZpbmcgaW52YWxpZCBuZWdhdGl2ZSB2YWx1ZXMgZnJvbSB0aGUgQ2Vuc3VzIGRhdGEsIHRoZSBkaXN0cmlidXRpb24gc2hvd3MgYSByaWdodC1za2V3ZWQgcGF0dGVybiwgd2l0aCBhIGZldyBoaWdoZXItaW5jb21lIHRyYWN0cyBvbiB0aGUgdXBwZXIgZW5kLioNCg0KDQoqRGlzdHJpYnV0aW9uIG9mIGFnZ3JhdmF0ZWQgYnVyZ2xhcmllcyBhY3Jvc3MgY2Vuc3VzIHRyYWN0cyoNCg0KYGBge3J9DQoNCiAgZ2dwbG90KHRyYWN0X2RhdGFfc2YsIGFlcyh4ID0gYnVyZ2xhcmllcykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBmaWxsID0gImZpcmVicmljayIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgQWdncmF2YXRlZCBCdXJnbGFyaWVzIiwNCiAgICB4ID0gIk51bWJlciBvZiBCdXJnbGFyaWVzIiwNCiAgICB5ID0gIkNvdW50IG9mIENlbnN1cyBUcmFjdHMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KKk1vc3QgY2Vuc3VzIHRyYWN0cyBleHBlcmllbmNlZCByZWxhdGl2ZWx5IGZldyBhZ2dyYXZhdGVkIGJ1cmdsYXJpZXMgaW4gMjAyMywgd2l0aCB0aGUgbWFqb3JpdHkgZmFsbGluZyBiZXR3ZWVuIDAgYW5kIDEwIGluY2lkZW50cy4gQSBzbWFsbCBudW1iZXIgb2YgdHJhY3RzIGhhZCBzdWJzdGFudGlhbGx5IGhpZ2hlciBjb3VudHMsIGNyZWF0aW5nIGEgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbi4gVGhpcyBzdWdnZXN0cyB0aGF0IGFnZ3JhdmF0ZWQgYnVyZ2xhcmllcyBhcmUgY29uY2VudHJhdGVkIGluIHNwZWNpZmljIGFyZWFzIHJhdGhlciB0aGFuIGV2ZW5seSBkaXN0cmlidXRlZCBhY3Jvc3MgdGhlIGNvdW50eS4qDQoNCg0KKipSZWxhdGlvbnNoaXAgQmV0d2VlbiBNZWRpYW4gSW5jb21lIGFuZCBBZ2dyYXZhdGVkIEJ1cmdsYXJ5IENvdW50cyoqDQoNCmBgYHtyfQ0KdHJhY3RfZGF0YV9zZiB8Pg0KICBmaWx0ZXIoIWlzLm5hKG1lZGlhbl9pbmNvbWUpKSB8PiAgICAgIyByZW1vdmUgbWlzc2luZyBpbmNvbWVzDQogIGdncGxvdChhZXMoeCA9IG1lZGlhbl9pbmNvbWUsIHkgPSBidXJnbGFyaWVzKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC43KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGZvcm11bGEgPSB5fngsIHNlID0gRkFMU0UsIGNvbG9yID0gImJsdWUiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTWVkaWFuIEluY29tZSB2cy4gQWdncmF2YXRlZCBCdXJnbGFyeSBDb3VudHMiLA0KICAgIHggPSAiTWVkaWFuIEluY29tZSIsDQogICAgeSA9ICJOdW1iZXIgb2YgQnVyZ2xhcmllcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoqVG8gZXhwbG9yZSB3aGV0aGVyIGluY29tZSBpcyByZWxhdGVkIHRvIGFnZ3JhdmF0ZWQgYnVyZ2xhcnkgYWN0aXZpdHksIEkgY3JlYXRlZCBhIHNjYXR0ZXJwbG90IGNvbXBhcmluZyBtZWRpYW4gaW5jb21lICh4LWF4aXMpIHdpdGggdGhlIG51bWJlciBvZiBidXJnbGFyaWVzIGluIGVhY2ggY2Vuc3VzIHRyYWN0ICh5LWF4aXMpLiBFYWNoIHBvaW50IHJlcHJlc2VudHMgYSBjZW5zdXMgdHJhY3QsIGFuZCBhIGxpbmVhciB0cmVuZCBsaW5lIGlzIGluY2x1ZGVkIHRvIHNob3cgdGhlIG92ZXJhbGwgZGlyZWN0aW9uIG9mIHRoZSByZWxhdGlvbnNoaXAuKg0KDQoNCiMjIyBQYXJ0IDM6IFN0YXRpc3RpY2FsIE1vZGVsaW5nDQoNCioqRml0IGEgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsIHdpdGggdGFyZ2V0IHZhcmlhYmxlIHRoZSByYXRlIG9mIGJ1cmdsYXJpZXMgcGVyIGNlbnN1cyB0cmFjdCBhbmQgd2l0aCBwcmVkaWN0b3IgdGhlIG1lZGlhbiBpbmNvbWUuKiogKipPZmZzZXQgdXNpbmcgdGhlIGxvZyBvZiB0aGUgcG9wdWxhdGlvbiBzbyB0aGF0IHdlIGFyZSBsb29raW5nIGF0IHRoZSByYXRlIG9mIGJ1cmdsYXJpZXMgcGVyIHBvcHVsYXRpb24gaW5zdGVhZCBvZiB0aGUgbnVtYmVyIG9mIGJ1cmdsYXJpZXMuKiogKipIb3cgY2FuIHlvdSBpbnRlcnByZXQgdGhlIG1lYW5pbmcgb2YgdGhlIG91dHB1dD8gSG93IGRvIHRoZSBlc3RpbWF0ZXMgZnJvbSB0aGUgbW9kZWwgY29tcGFyZSB0byB0aGUgb2JzZXJ2ZWQgZGF0YT8qKg0KDQoNCg0KYGBge3J9DQptb2RlbDEgPC0gdHJhY3RfZGF0YV9zZiB8Pg0KICBzdF9kcm9wX2dlb21ldHJ5KCkgfD4NCiAgZmlsdGVyKA0KICAgIHBvcHVsYXRpb24+MCwNCiAgICAhaXMubmEobWVkaWFuX2luY29tZSksDQogICAgIWlzLm5hKGJ1cmdsYXJpZXMpDQogICkNCmBgYA0KDQoNCioqUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsIGZpdCoqDQoNCmBgYHtyfQ0KbW9kZWwyIDwtIGdsbSgNCiAgYnVyZ2xhcmllcyB+IG1lZGlhbl9pbmNvbWUgKyBvZmZzZXQobG9nKHBvcHVsYXRpb24pKSwNCiAgZGF0YSA9IG1vZGVsMSwNCiAgZmFtaWx5ID1wb2lzc29uKCkNCikNCg0Kc3VtbWFyeShtb2RlbDIpDQpgYGANCipJbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50IG9uIG1lZGlhbl9pbmNvbWUqDQoNCmBgYHtyfQ0KbW9kZWwyIHw+IGNvZWYoKSB8PiBleHAoKQ0KYGBgDQoqSW5jb21lIGlzIG1lYXN1cmVkIGluIGRvbGxhcnMsIHNvIHRoZSBlZmZlY3QgcGVyICQxIGlzIHRvbyBzbWFsbCB0byBpbnRlcnByZXQuIEkgc2NhbGVkIHRoZSBjb2VmZmljaWVudCB0byBhICQxMCwwMDAgaW5jcmVhc2UsIHdoaWNoIG1ha2VzIHRoZSByYXRlIHJhdGlvIG1lYW5pbmdmdWwgd2hpbGUgc3RpbGwgdXNpbmcgdGhlIHNhbWUgZXhwb25lbnRpYXRpb24gYXBwcm9hY2ggdGF1Z2h0IGluIGNsYXNzLioNCg0KYGBge3J9DQpleHAoMTAwMDAgKiBjb2VmKG1vZGVsMilbIm1lZGlhbl9pbmNvbWUiXSkNCmBgYA0KKkZvciB0d28gY2Vuc3VzIHRyYWN0cyB3aXRoIHRoZSBzYW1lIHBvcHVsYXRpb24sIHRoZSB0cmFjdCB3aXRoIGEgJDEwLDAwMCBoaWdoZXIgbWVkaWFuIGluY29tZSBpcyBlc3RpbWF0ZWQgdG8gaGF2ZSBhIGJ1cmdsYXJ5IHJhdGUgdGhhdCBpcyBhYm91dCAyMS42JSBsb3dlci4qDQoNCg0KYGBge3J9DQpwcmVkaWN0KA0KICBtb2RlbDIsDQogIG5ld2RhdGEgPSB0aWJibGUoDQogICAgbWVkaWFuX2luY29tZSA9IGMoMzAwMDAsIDgwMDAwKSwgICANCiAgICBwb3B1bGF0aW9uID0gYyg0MDAwLCA0MDAwKSAgICAgICAgDQogICksDQogIHR5cGUgPSAicmVzcG9uc2UiDQopDQpgYGANCipGb3IgdHdvIGNlbnN1cyB0cmFjdHMgd2l0aCB0aGUgc2FtZSBwb3B1bGF0aW9uIG9mIDQsMDAwIHBlb3BsZSwgdGhlIG1vZGVsIHByZWRpY3RzIGFib3V0IDEwLjggYWdncmF2YXRlZCBidXJnbGFyaWVzIGluIGEgdHJhY3Qgd2l0aCBhIG1lZGlhbiBpbmNvbWUgb2YgJDMwLDAwMCwgY29tcGFyZWQgdG8gYWJvdXQgMy4yIGJ1cmdsYXJpZXMgaW4gYSB0cmFjdCB3aXRoIGEgbWVkaWFuIGluY29tZSBvZiAkODAsMDAwLiBUaGlzIHJlZmxlY3RzIHRoZSBtb2RlbOKAmXMgZXN0aW1hdGUgdGhhdCBoaWdoZXItaW5jb21lIGFyZWFzIGhhdmUgc3Vic3RhbnRpYWxseSBsb3dlciBidXJnbGFyeSByYXRlcy4qDQoNCiMjIEZpbmFsIFN1bW1hcnkNCg0KKiBDb252ZXJ0ZWQgYnVyZ2xhcnkgZGF0YSBpbnRvIGFuIHNmIG9iamVjdCBhbmQgYWxpZ25lZCBpdHMgQ1JTIHdpdGggdGhlIGNlbnN1cyB0cmFjdCBzaGFwZWZpbGUuDQoqIFBlcmZvcm1lZCBhIHNwYXRpYWwgam9pbiB0byBhc3NpZ24gZWFjaCBidXJnbGFyeSBpbmNpZGVudCB0byBhIGNlbnN1cyB0cmFjdC4NCiogUmVtb3ZlZCBkdXBsaWNhdGUgaW5jaWRlbnRzIGFuZCBjb3VudGVkIHVuaXF1ZSBidXJnbGFyaWVzIHBlciB0cmFjdC4NCiogTWVyZ2VkIGJ1cmdsYXJ5IGNvdW50cyB3aXRoIGNlbnN1cyBwb3B1bGF0aW9uIGFuZCBtZWRpYW4gaW5jb21lIGRhdGEuDQoqIENsZWFuZWQgdGhlIG1lZGlhbiBpbmNvbWUgZmllbGQgYnkgcmVtb3ZpbmcgaW52YWxpZCBuZWdhdGl2ZSB2YWx1ZXMuDQoqIEV4cGxvcmVkIHRoZSBkYXRhIHRocm91Z2ggaGlzdG9ncmFtcyBhbmQgc2NhdHRlcnBsb3RzLCBvYnNlcnZpbmcgdGhhdCBsb3dlci1pbmNvbWUgdHJhY3RzIHRlbmQgdG8gaGF2ZSBtb3JlIGJ1cmdsYXJpZXMuDQoqIEZpdCBhIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCB3aXRoIGFuIG9mZnNldCBmb3IgcG9wdWxhdGlvbiB0byBtb2RlbCBidXJnbGFyeSByYXRlcy4NCiogRm91bmQgYSBzaWduaWZpY2FudCBuZWdhdGl2ZSByZWxhdGlvbnNoaXA6IGhpZ2hlciBpbmNvbWUg4oaSIGxvd2VyIGJ1cmdsYXJ5IHJhdGUuDQoqIEEgJDEwLDAwMCBpbmNyZWFzZSBpbiBtZWRpYW4gaW5jb21lIGlzIGFzc29jaWF0ZWQgd2l0aCBhYm91dCBhIDIxLjYlIGRlY3JlYXNlIGluIHRoZSBleHBlY3RlZCBidXJnbGFyeSByYXRlLg0KKiBNb2RlbCBwcmVkaWN0aW9ucyBjb25maXJtIHRoYXQgaGlnaGVyLWluY29tZSB0cmFjdHMgaGF2ZSBzdWJzdGFudGlhbGx5IGZld2VyIGV4cGVjdGVkIGJ1cmdsYXJpZXMgdGhhbiBsb3dlci1pbmNvbWUgdHJhY3RzLg==